// SPDX-License-Identifier: GPL-2.0-or-later
#include <scsi/scsi.h>
#include <scsi/scsi_cmnd.h>
#include <asm/unaligned.h>

#include "cdb.h"

#define MLOG_MASK_PREFIX ML_ADL
#include "cluster/masklog.h"

#define COMPARE_AND_WRITE_OPCODE 0x89

struct cdb_request {
	int	error;
	struct completion waiting;
};

void setup_compare_and_write_cmd(struct caw_cdb *caw,
		sector_t sector, unsigned int nr_sectors)
{
	memset(caw, 0x0, sizeof(struct caw_cdb));
	caw->opc = COMPARE_AND_WRITE_OPCODE;
	put_unaligned_be64(sector, caw->lba);
	caw->length = (unsigned char)(nr_sectors & 0xff);
}

/* return -EAGAIN if miscompare with the data on disk */
static int cdb_check_sense(struct request *rq)
{
	char *sense_buffer = scsi_req(rq)->sense;
	int ret = -EIO;

	if (!sense_buffer)
		goto bail;

	if (status_byte(scsi_req(rq)->result) == CHECK_CONDITION) {
		u8 response_code = (sense_buffer[0] & 0x7f);
		u8 sense_key;

		/* sense invalid, return EIO */
		if ((response_code & 0x70) != 0x70) {
			ret = -EIO;
			goto bail;
		}
		if (response_code >= 0x72)
			sense_key = (sense_buffer[1] & 0xf);
		else
			sense_key = (sense_buffer[2] & 0xf);

		if (sense_key == MISCOMPARE)
			ret = -EAGAIN;
	}

bail:
	return ret;
}

static void cdb_end_rq(struct request *rq, blk_status_t error)
{
	struct cdb_request *cdb_rq = rq->end_io_data;
	int result = scsi_req(rq)->result;
	if (result) {
		cdb_rq->error = cdb_check_sense(rq);
		if (cdb_rq->error != -EAGAIN) {
			mlog(ML_ERROR,
				"scsi_req(rq)->result 0x%x, status_byte 0x%x, host_byte 0x%x, driver_byte 0x%x\n",
				result, status_byte(result),
				host_byte(result), driver_byte(result));
		}
	} else if (error) {
		mlog_errno(blk_status_to_errno(error));
		cdb_rq->error = -EIO;
	}

	complete(&cdb_rq->waiting);
	rq->end_io_data = NULL;
	blk_put_request(rq);
}

int cdb_exec(struct block_device *bdev, int rw,
		char *buf, unsigned int buf_len,
		unsigned char *cmd)
{
	int ret;
	struct cdb_request cdb_rq;
	struct request *rq;
	struct request_queue *q;

	if (!bdev->bd_disk) {
		ret = -EINVAL;
		mlog_errno(ret);
		return ret;
	}

	q = bdev->bd_disk->queue;
	if (!q) {
		ret = -EINVAL;
		mlog_errno(ret);
		return ret;
	}

	if (buf_len > (queue_max_hw_sectors(q) << 9) || !buf_len)
		return -EINVAL;

	rq = blk_get_request(q, rw, 0);
	if (IS_ERR(rq)) {
		ret = PTR_ERR(rq);
		mlog_errno(ret);
		return ret;
	}

	cdb_rq.error = 0;
	init_completion(&cdb_rq.waiting);

	scsi_req(rq)->cmd_len = COMMAND_SIZE(cmd[0]);
	memcpy(scsi_req(rq)->cmd, cmd, scsi_req(rq)->cmd_len);
	rq->__sector = 0;
	rq->timeout = START_STOP_TIMEOUT;
	scsi_req(rq)->retries = 0;

	ret = blk_rq_map_kern(q, rq, buf, buf_len, GFP_NOFS);
	if (ret) {
		mlog_errno(ret);
		blk_put_request(rq);
		return ret;
	}

	rq->end_io_data = &cdb_rq;
	blk_execute_rq_nowait(q, bdev->bd_disk, rq, 0, cdb_end_rq);
	wait_for_completion(&cdb_rq.waiting);

	return cdb_rq.error;
}
